<body></body>
<script>
  // 存储副作用函数的桶
  const bucket = new WeakMap();
  const ITERATE_KEY = Symbol();

  let shouldTrack = true;

  function track(target, key) {
    if (!activeEffect || !shouldTrack) return;
    let depsMap = bucket.get(target);
    if (!depsMap) {
      bucket.set(target, (depsMap = new Map()));
    }
    let deps = depsMap.get(key);
    if (!deps) {
      depsMap.set(key, (deps = new Set()));
    }
    deps.add(activeEffect);
    activeEffect.deps.push(deps);
  }

  function trigger(target, key, type, newVal) {
    console.log("trigger", key);
    const depsMap = bucket.get(target);
    if (!depsMap) return;
    const effects = depsMap.get(key);

    const effectsToRun = new Set();
    effects &&
      effects.forEach((effectFn) => {
        if (effectFn !== activeEffect) {
          effectsToRun.add(effectFn);
        }
      });

    if (
      type === "ADD" ||
      type === "DELETE" ||
      (type === "SET" &&
        Object.prototype.toString.call(target) === "[object Map]")
    ) {
      const iterateEffects = depsMap.get(ITERATE_KEY);
      iterateEffects &&
        iterateEffects.forEach((effectFn) => {
          if (effectFn !== activeEffect) {
            effectsToRun.add(effectFn);
          }
        });
    }

    if (type === "ADD" && Array.isArray(target)) {
      const lengthEffects = depsMap.get("length");
      lengthEffects &&
        lengthEffects.forEach((effectFn) => {
          if (effectFn !== activeEffect) {
            effectsToRun.add(effectFn);
          }
        });
    }

    if (Array.isArray(target) && key === "length") {
      depsMap.forEach((effects, key) => {
        if (key >= newVal) {
          effects.forEach((effectFn) => {
            if (effectFn !== activeEffect) {
              effectsToRun.add(effectFn);
            }
          });
        }
      });
    }

    effectsToRun.forEach((effectFn) => {
      if (effectFn.options.scheduler) {
        effectFn.options.scheduler(effectFn);
      } else {
        effectFn();
      }
    });
  }

  // 用一个全局变量存储当前激活的 effect 函数
  let activeEffect;
  // effect 栈
  const effectStack = [];

  function effect(fn, options = {}) {
    const effectFn = () => {
      cleanup(effectFn);
      // 当调用 effect 注册副作用函数时，将副作用函数复制给 activeEffect
      activeEffect = effectFn;
      // 在调用副作用函数之前将当前副作用函数压栈
      effectStack.push(effectFn);
      const res = fn();
      // 在当前副作用函数执行完毕后，将当前副作用函数弹出栈，并还原 activeEffect 为之前的值
      effectStack.pop();
      activeEffect = effectStack[effectStack.length - 1];

      return res;
    };
    // 将 options 挂在到 effectFn 上
    effectFn.options = options;
    // activeEffect.deps 用来存储所有与该副作用函数相关的依赖集合
    effectFn.deps = [];
    // 执行副作用函数
    if (!options.lazy) {
      effectFn();
    }

    return effectFn;
  }

  function cleanup(effectFn) {
    for (let i = 0; i < effectFn.deps.length; i++) {
      const deps = effectFn.deps[i];
      deps.delete(effectFn);
    }
    effectFn.deps.length = 0;
  }

  const instrumentations = {
    delete(key) {
      const target = this.raw;

      const res = target.delete(key);

      console.log(res);

      trigger(target, key, "DELETE");

      return res;
    }
  };

  const reactiveMap = new Map();
  function reactive(obj) {
    const proxy = createReactive(obj);

    const existionProxy = reactiveMap.get(obj);
    if (existionProxy) return existionProxy;

    reactiveMap.set(obj, proxy);

    return proxy;
  }

  const mutableInstrumentations = {
    add(key) {
      const target = this.raw;
      const hadKey = target.has(key);
      const res = target.add(key);
      if (!hadKey) {
        trigger(target, key, "ADD");
      }
      return res;
    },
    delete(key) {
      const target = this.raw;
      const hadKey = target.has(key);
      const res = target.delete(key);
      if (hadKey) {
        trigger(target, key, "DELETE");
      }
      return res;
    },
    get(key) {
      const target = this.raw;
      const had = target.has(key);

      track(target, key);

      if (had) {
        const res = target.get(key);
        return typeof res === "object" ? reactive(res) : res;
      }
    },
    set(key, value) {
      const target = this.raw;
      const had = target.has(key);

      const oldValue = target.get(key);
      const rawValue = value.raw || value;
      target.set(key, rawValue);

      if (!had) {
        trigger(target, key, "ADD");
      } else if (
        oldValue !== value ||
        (oldValue === oldValue && value === value)
      ) {
        trigger(target, key, "SET");
      }
    },
    forEach(callback) {
      const wrap = (val) => (typeof val === "object" ? reactive(val) : val);
      const target = this.raw;
      track(target, ITERATE_KEY);
      target.forEach((v, k) => {
        callback(wrap(v), wrap(k), this);
      });
    }
  };

  function createReactive(obj, isShallow = false, isReadonly = false) {
    return new Proxy(obj, {
      get(target, key, receiver) {
        if (key === "raw") return target;
        if (key === "size") {
          track(target, ITERATE_KEY);
          return Reflect.get(target, key, target);
        }

        return mutableInstrumentations[key];
      }
    });
  }

  // =================================================================

  const p = reactive(new Map([["key", 1]]));

  effect(() => {
    console.log("触发副作用函数");
    p.forEach(function (value, key) {
      console.log(value); // 1
    });
  });

  p.set("key", 2);
</script>
